package cn.com.twoke.game.common.utils;

import cn.com.twoke.game.common.constants.MessageType;
import cn.com.twoke.game.common.entity.message.Message;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

import static cn.com.twoke.game.common.constants.GameConfig.HEADER_SIZE;
import static cn.com.twoke.game.common.constants.GameConfig.MAX_PACKET_SIZE;

public final class MessageUtil {

    /**
     * 消息分片
     * @param message
     * @return
     * @throws IOException
     */
    private static List<ByteBuffer> segmentMessage(Message message) throws IOException {
        List<ByteBuffer> packets = new ArrayList<>();
        byte[] messageBytes = message.serialize();
        int totalLength = messageBytes.length;
        int numPackets = (int) Math.ceil((double) totalLength / MAX_PACKET_SIZE);


        for (int i = 0; i < numPackets; i++) {
            int start = i * MAX_PACKET_SIZE;
            int length = Math.min(MAX_PACKET_SIZE, totalLength - start);

            ByteBuffer packet = ByteBuffer.allocate(HEADER_SIZE + length);
            packet.putInt(totalLength); // 消息的总长度
            packet.putInt(i); // 包的序号
            packet.putInt(length); // 该包的数据长度
            packet.put(messageBytes, start, length);
            packet.flip();
            packets.add(packet);
        }

        return packets;
    }


    /**
     * 消息组装
     * @param packets
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static <T extends Message> T assembleMessage(List<ByteBuffer> packets) throws IOException, ClassNotFoundException {
        int totalLength = 0;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        for (ByteBuffer packet : packets) {
            packet.rewind();
            int packetTotalLength = packet.getInt();
            if (totalLength == 0) {
                totalLength = packetTotalLength;
            }
            packet.getInt(); // 跳过包的序号
            int packetDataLength = packet.getInt(); // 读取该包的数据长度
            byte[] packetData = new byte[packetDataLength];
            packet.get(packetData);
            baos.write(packetData);
        }

        return Message.<T>deserialize(baos.toByteArray());
    }

    /**
     * 判断消息是否完整
     * @param packets
     * @param totalLength
     * @return
     */
    private static boolean isMessageComplete(List<ByteBuffer> packets, int totalLength) {
        int receivedLength = 0;
        for (ByteBuffer packet : packets) {
            packet.rewind();
            packet.getInt(); // 跳过总长度
            packet.getInt(); // 跳过包的序号
            int packetLength = packet.getInt(); // 该包的数据长度
            receivedLength += packetLength;
        }
        return receivedLength >= totalLength;
    }


    /**
     * 读取消息
     * @param channel
     * @return
     * @param <T>
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T extends Message> T readMessage(SocketChannel channel) throws IOException, ClassNotFoundException {
        List<ByteBuffer> receivedPackets = new ArrayList<>();
        T message = null;
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE + MAX_PACKET_SIZE);
            try {
                int bytesRead = channel.read(buffer);
                if (bytesRead == -1) {
                    break;
                }
            } catch (IOException e) {
                break;
            }
            buffer.flip();

            int totalLength = buffer.getInt();
            int packetIndex = buffer.getInt();
            int packetDataLength = buffer.getInt();

            byte[] packetData = new byte[packetDataLength];
            buffer.get(packetData);

            ByteBuffer packet = ByteBuffer.allocate(HEADER_SIZE + packetDataLength);
            packet.putInt(totalLength);
            packet.putInt(packetIndex);
            packet.putInt(packetDataLength);
            packet.put(packetData);
            packet.flip();
            receivedPackets.add(packet);
            if (isMessageComplete(receivedPackets, totalLength)) {
                message = assembleMessage(receivedPackets);
                break;
            }
        }
        return message;
    }


    /**
     * 写入消息
     * @param channel
     * @param message
     * @throws IOException
     */
    public static void writeMessage(SocketChannel channel, Message message) {
       try {
           List<ByteBuffer> packets = MessageUtil.segmentMessage(message);
           // 发送分包
           for (ByteBuffer packet : packets) {
               channel.write(packet);
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
    }
}
